import { Shader } from "@nirvana/shaders";
import { Matrix4, Vector2, Matrix3, Vector3, Vector4 } from "@nirvana/math";
import { WebGLCanvas } from "@nirvana/canvas";
import {
  Data,
  Mesh,
  SceneNode,
  DataElement,
  Geometry,
  ShaderMaterial,
  Camera,
  AmbientLight,
  DirectionalLight,
  PointLight,
  TextureUtil,
  SimpleMaterial,
  WireframeMesh
} from "@nirvana/core";
import * as Common from "@loaders/commons";
import { OBJLoader } from "@loaders/obj";
import { GLTFLoader } from "@loaders/gltf";
import { ParticleSystem } from "@nirvana/particles";

async function main() {
  // 使用使用 Canvas
  let canvas = document.getElementById("engine-nirvana") as HTMLCanvasElement;
  const webCanvas = new WebGLCanvas(canvas, {});
  const engine = webCanvas.engine;
  const scene = engine.sceneManager.createScene("root");
  scene.addLight(new AmbientLight(""));
  scene.addLight(new DirectionalLight(""));
  scene.addLight(new PointLight(""));
  scene.addLight(new PointLight(""));
  const camera = new Camera("root camera", engine);
  scene.addCamera(camera);
  camera.transform.setPosition(0, 0, 10);
  const rootEntity = new SceneNode("root entity");
  scene.addEntity(rootEntity);
  // rootEntity.transform.scale(new Vector3(0.3, 0.3, 0.3));

  const gl = engine.gl;

  // const objHref = "/dev-static/gltf/1.0/Box/glTF-Embedded/Box.gltf";
  // const objHref = "/dev-static/gltf/1.0/Box/glTF-MaterialsCommon/Box.gltf";
  // const objHref = "/dev-static/gltf/1.0/Box/glTF-Binary/Box.glb";
  // const objHref = "/dev-static/gltf/1.0/Box/glTF/Box.gltf";
  // const objHref = "/dev-static/gltf/2.0/Box/glTF-Embedded/Box.gltf";
  // const objHref = "/dev-static/gltf/2.0/Box/glTF-pbrSpecularGlossiness/Box.gltf";
  // const objHref = "/dev-static/gltf/2.0/Box/glTF-Binary/Box.glb";
  // const objHref = "/dev-static/gltf/2.0/Box/glTF/Box.gltf";
  // const objHref = "/dev-static/gltf/1.0/BoxTextured/glTF/BoxTextured.gltf";
  // const objHref = "/dev-static/gltf/2.0/BoxTextured/glTF-Binary/BoxTextured.glb";
  // const objHref = "/dev-static/gltf/1.0/BoxTextured/glTF-Embedded/BoxTextured.gltf";
  // const objHref = "/dev-static/gltf/1.0/BoxTextured/glTF-MaterialsCommon/BoxTextured.gltf";

  const objHref = "/dev-static/gltf/2.0/Duck/glTF/Duck.gltf";

  // const objHref = "/dev-static/obj/elphant/index.gltf";
  // const objHref = "/dev-static/gltf/other/01/dieselpunk_hovercraft.gltf";

  const textures = {
    defaultWhite: TextureUtil.createTexture(gl, { src: [255, 255, 255, 255] })
  };
  const defaultMaterial = {
    diffuse: [1, 1, 1],
    diffuseMap: textures.defaultWhite,
    ambient: [0, 0, 0],
    specular: [1, 1, 1],
    specularMap: textures.defaultWhite,
    shininess: 400,
    opacity: 1
  };

  new GLTFLoader(objHref, (scene) => {
    // debugger
    scene.nodes.forEach((node) => {
      node.children.forEach((enNode) => {
        enNode.children.forEach(({ idOrName, meshes }) => {
          // debugger;
          meshes.primitives.forEach(({ attributes, indices, mode, material, program }) => {
            const gem = new Geometry();
            gem.addData(new Data(new Float32Array(attributes.POSITION), [new DataElement("POSITION", 3)]), "pos");
            if (attributes.NORMAL && attributes.NORMAL.length) {
              gem.addData(
                new Data(new Float32Array(attributes.NORMAL), [new DataElement("NORMAL", 3, gl.FLOAT, false, 0, 0)]),
                "normal"
              );
            }
            if (attributes.TEXCOORD_0 && attributes.TEXCOORD_0.length) {
              gem.addData(
                new Data(new Float32Array(attributes.TEXCOORD_0), [
                  new DataElement("TEXCOORD_0", 2, gl.FLOAT, false, 0, 0)
                ]),
                "texcoord"
              );
            }

            if (indices && indices.length) {
              gem.addData(new Data(new Uint16Array(indices)), "ind");
            }

            let matl;
            if (program) {
              const shader = Shader.create(material.name, program.vertexShader, program.fragmentShader);
              matl = new ShaderMaterial(material.name, shader);
            } else {
              matl = new SimpleMaterial(material.name, {
                normal: !!attributes.NORMAL,
                uv: !!attributes.TEXCOORD_0
              });
            }

            const shaderData = matl.shaderData;
            const { parameters, uniforms = {} } = material.technique || {};
            Object.keys(uniforms).forEach((uniform) => {
              const joinName = uniforms[uniform];
              const sfix = parameters[joinName];
              switch (sfix.type) {
                case WebGLRenderingContext.FLOAT_MAT2:
                case WebGLRenderingContext.FLOAT_MAT3:
                  {
                    console.log(uniform, "FLOAT_MAT3");
                    shaderData.setFloatArray(uniform, Matrix3.e);
                  }
                  break;
                case WebGLRenderingContext.FLOAT_MAT4:
                  {
                    console.log(uniform, "FLOAT_MAT4");
                    // if (sfix.semantic === "MODELVIEWINVERSETRANSPOSE") {

                    // } else if (sfix.semantic === "MODELVIEW") {

                    // } else if (sfix.semantic === "PROJECTION") {

                    // } else if (sfix.semantic === "NORMAL") {

                    // }
                    shaderData.setFloatArray(uniform, Matrix4.e);
                    // shaderData.setMatrix(uniform, { elements: [0] });
                  }
                  break;
                case WebGLRenderingContext.FLOAT_VEC2:
                  {
                    console.log(uniform, "FLOAT_VEC2");
                    shaderData.setVector2(uniform, Vector2.fromArray(material.values[joinName]));
                    // shaderData.setFloatArray(uniform, material.values[joinName]);
                  }
                  break;
                case WebGLRenderingContext.FLOAT_VEC3:
                  {
                    console.log(uniform, "FLOAT_VEC3");
                    shaderData.setVector3(uniform, Vector3.fromArray(material.values[joinName]));
                    // shaderData.setFloatArray(uniform, );
                  }
                  break;
                case WebGLRenderingContext.FLOAT_VEC4:
                  {
                    console.log(uniform, "FLOAT_VEC4");
                    // shaderData.setVector4(uniform, { x: 1, y: 0, z: 0, w: 1 });
                    // shaderData.setFloatArray(uniform, {elements:[1, 0, 0, 1]});
                    shaderData.setVector4(uniform, Vector4.fromArray(material.values[joinName]));
                  }
                  break;
                case WebGLRenderingContext.FLOAT:
                  {
                    console.log(uniform, "FLOAT");
                    shaderData.setFloat(uniform, material.values[joinName]);
                  }
                  break;
                case WebGLRenderingContext.SAMPLER_2D:
                  {
                    let val = material.values[joinName];
                    let texture;
                    let texture1;
                    if (typeof val === "string") {
                      const info = scene.textures[val];
                      let source;
                      if (/^data:/.test(info.source)) {
                        source = info.source;
                      } else {
                        source = Common.relativePath(objHref, info.source);
                      }

                      texture = TextureUtil.createTexture(gl, {
                        src: source,
                        color: [1, 1, 1, 1],
                        flipY: true
                      });

                      // texture1 = TextureUtil.createTexture(gl, {
                      //   src: source,
                      //   color: [1, 1, 1, 1],
                      //   flipY: true
                      // });
                    }
                    shaderData.setTexture(uniform, { _glTexture: texture, _target: gl.TEXTURE_2D });
                    // shaderData.setTextureArray(uniform, [
                    //   { _glTexture: texture, _target: gl.TEXTURE_2D },
                    //   // { _glTexture: texture, _target: gl.TEXTURE_2D },
                    //   // { _glTexture: texture, _target: gl.TEXTURE_2D },
                    //   // { _glTexture: texture, _target: gl.TEXTURE_2D },
                    //   // { _glTexture: texture, _target: gl.TEXTURE_2D },
                    //   // { _glTexture: texture, _target: gl.TEXTURE_2D }
                    // ]);
                  }
                  break;
                case WebGLRenderingContext.SAMPLER_CUBE:
                  {
                    console.log(uniform, "SAMPLER_CUBE");
                    // shaderData.setTexture(uniform, { _glTexture: textures.defaultWhite, _target: gl.TEXTURE_CUBE_MAP });
                    shaderData.setTextureArray(uniform, [
                      { _glTexture: textures.defaultWhite, _target: gl.TEXTURE_CUBE_MAP },
                      { _glTexture: textures.defaultWhite, _target: gl.TEXTURE_CUBE_MAP },
                      { _glTexture: textures.defaultWhite, _target: gl.TEXTURE_CUBE_MAP },
                      { _glTexture: textures.defaultWhite, _target: gl.TEXTURE_CUBE_MAP },
                      { _glTexture: textures.defaultWhite, _target: gl.TEXTURE_CUBE_MAP },
                      { _glTexture: textures.defaultWhite, _target: gl.TEXTURE_CUBE_MAP },
                      { _glTexture: textures.defaultWhite, _target: gl.TEXTURE_CUBE_MAP }
                    ]);
                  }
                  break;
              }
              // console.log(uniform)
            });
            const mesh = new Mesh(gem, matl, "xxx");
            // mesh.drawMode = mode;
            rootEntity.addChildNode(mesh);
            mesh.transform.setScaling(0.01, 0.01, 0.01);
            // rootEntity.addChildNode(new WireframeMesh(mesh));
          });
        });
      });
    });
  });

  engine.update = () => {
    // const program = window.matl.getProgram(engine.gl);
    const r = rootEntity.findNodeByName("xxx");
    // debugger
    // console.log(r)
    if (r === undefined) return;
    const shaderData = r.material.shaderData;
    // // program.bind();
    const localMatrix = rootEntity.transform.localMatrix;
    const modelMatrix = rootEntity.transform.modelMatrix;
    const viewMatrix = camera.transform.viewMatrix;
    const projMatrix = camera.projMatrix;
    const mvMatrix = viewMatrix.clone().dot(modelMatrix);
    const mvpMatrix = projMatrix.clone().dot(mvMatrix);
    const normalMatrix = mvMatrix.clone().inverse().transpose();
    const invMatrix = modelMatrix.clone().inverse();
    // shaderData.setVector3("u_cameraPos", camera.transform.position);
    shaderData.setMatrix("u_projectionMatrix", projMatrix);
    // shaderData.setMatrix("u_viewMat", viewMatrix);
    // shaderData.setMatrix("u_modelMat", modelMatrix);
    // shaderData.setMatrix("u_localMat", localMatrix);
    // shaderData.setMatrix("u_worldMat", rootEntity.worldMatrix);
    shaderData.setMatrix("u_normalMatrix", Matrix3.fromMat4(normalMatrix));
    shaderData.setMatrix("u_modelViewMatrix", mvMatrix);
    // shaderData.setMatrix("u_MVPMat", mvpMatrix);
    // shaderData.setMatrix("u_invMat", invMatrix);
    rootEntity.transform.rotate(0, 0.01, 0);
  };
}

main();
